package com.xunhang.utils;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class CacheClient {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    Cache<String,Object> cache= Caffeine.newBuilder().maximumSize(1000).build();

    @Autowired
    private RedissonClient redissonClient;
    private static final ExecutorService CACHE_BUILD_EXECUTORS= Executors.newFixedThreadPool(10);
    public <R,ID> boolean save(String keyPrefix, ID id, R data, RBloomFilter<ID> bloomFilter, Function<R, Boolean> Save){
        String key=keyPrefix+id;
        bloomFilter.add(id);
        return Save.apply(data);
    }


    public void set(String key, Object data, Long time, TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key,data,timeUnit.toSeconds(time),TimeUnit.SECONDS);
    }

    public void setWithAvalanche(String key,Object data,Long time,TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key,data,timeUnit.toSeconds(time)+ RandomUtil.randomInt(1),TimeUnit.SECONDS);
    }


    public void setWithLogicalExpired(String key, Object data, Long time, TimeUnit timeUnit){
        RedisData redisData=new RedisData();
        redisData.setData(data);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        String json= JSONUtil.toJsonStr(redisData);
        stringRedisTemplate.opsForValue().set(key,json);
    }


    public <R,ID> Boolean updateInDataBase(String keyPrefix, ID id, R data,RBloomFilter<ID> bloomFilter,Function<R, Boolean> Update){
        String key=keyPrefix+id;
        if(!bloomFilter.contains(id)){
            return false;
        }
        Update.apply(data);
        stringRedisTemplate.delete(key);
        return true;
    }
    public <R,ID> Boolean updateInDataBaseWithCaffeine(String keyPrefix, ID id, R data,RBloomFilter<ID> bloomFilter,Function<R, Boolean> Update){
        String key=keyPrefix+id;
        if(!bloomFilter.contains(id)){
            return false;
        }
        Update.apply(data);
        stringRedisTemplate.delete(key);
        cache.invalidate(key);
        return true;
    }

    public <ID> Boolean deleteInDataBase(String keyPrefix, ID id,RBloomFilter<ID> bloomFilter,Function<Integer, Boolean> Delete){
        String key=keyPrefix+id;
        if(!bloomFilter.contains(id)){
            return false;
        }
        Delete.apply((Integer) id);
        stringRedisTemplate.delete(key);
        return true;
    }
    public <ID> Boolean deleteInDataBaseWithCaffeine(String keyPrefix, ID id,RBloomFilter<ID> bloomFilter,Function<String, Boolean> Delete){
        String key=keyPrefix+id;
        if(!bloomFilter.contains(id)){
            return false;
        }
        Delete.apply(key);
        stringRedisTemplate.delete(key);
        cache.invalidate(key);
        return true;
    }


    public <R,ID> R queryWithPassThroughAndCaffeine(String keyPrefix, ID id, RBloomFilter<ID> bloomFilter, Function<ID,R> DBGetById,Class rclass){
        String key=keyPrefix+id;
        R r=(R)cache.getIfPresent(key);
        if(r==null){
            r= queryWithPassThrough(keyPrefix,id,bloomFilter,DBGetById,rclass);
        }
        return r;
    }

    public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, RBloomFilter<ID> bloomFilter, Function<ID,R> DBGetById,Class rclass){
        String key=keyPrefix+id;
        if(bloomFilter!=null){
            if(!bloomFilter.contains(id)){
                System.out.println("not exist in bloomFilter ");
                return null;
            }
        }
//        Object o = redisTemplate.opsForValue().get(key);
//        R result=(R) o;
        String s = stringRedisTemplate.opsForValue().get(key);
        System.out.println(s);
        R result=null;
        if(s!=null) {
            result = (R) JSONUtil.toBean(s, rclass);
        }
        if(result!=null){
            System.out.println("in redis");
            return result;
        }
        if(DBGetById==null){
            return null;
        }
        result=DBGetById.apply(id);
        if(result==null){
            System.out.println("no in database");
            return null;
        }else {
//            System.out.println("result is in data base"+result);
            String json=JSONUtil.toJsonStr(result);
            stringRedisTemplate.opsForValue().set(key,json);
        }
        return result;
    }

    public <R> void initialBloomFilter(BaseMapper<R> mapper, RBloomFilter bloomFilter, Class<R> rClass){
        Wrapper<R> wrapper=new QueryWrapper<R>().select("id");
        List<R> rs = mapper.selectList(wrapper);
        try {
            Field idField =rClass.getDeclaredField("id");
            idField.setAccessible(true);
            for (R object : rs) {
                Integer idValue = (Integer) idField.get(object);
                bloomFilter.add(idValue);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public  <R,ID> R queryWithLogicExpired(String keyPrefix, ID id, RBloomFilter<ID> bloomFilter, Function<ID,R> DBGetById) throws InterruptedException {
        String key=keyPrefix+id;
        RedisData redisData= queryWithPassThrough(keyPrefix,id,bloomFilter, id2->null,RedisData.class);
        if(redisData==null){
            return null;
        }
        R object=(R)redisData.getData();
        if(redisData.getExpireTime().isAfter(LocalDateTime.now())){
            return object;
        }
        RLock lock = redissonClient.getLock(key);
        if(lock.tryLock(10,0,TimeUnit.SECONDS)){
            CACHE_BUILD_EXECUTORS.submit(()->
            {
                try {
                    setWithLogicalExpired(key,object,30L,TimeUnit.MINUTES);
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            });
        }
        return object;
    }

}
